unit AfterDownload_Defines;

interface

uses
 Windows,
 Classes,
 SysUtils,
 ShellAPI,
 Locales;

type

 TDoActionFunc = function(const Cmd, Params: String): String of object;
 TCatInfo = function(const CatID: String; All: Boolean = false): String of object;

 Condition = class(TCollectionItem)
  private
   FCondIndex: Char; //A..Z
   FCondType: Integer; //one of cnd*
   FParameter: String;
   FExternal: Boolean;
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   procedure Clear;
   property ExternalFlag: Boolean read FExternal write FExternal;
  published
   property CondIndex: Char read FCondIndex write FCondIndex;
   property CondType: Integer read FCondType write FCondType;
   property Parameter: String read FParameter write FParameter;
 end;

 TConditions = class(TCollection)
  private
   function GetItem(Index: Integer): Condition;
   procedure SetItem(Index: Integer; const Value: Condition);
  public
   function Add: Condition;
   function Insert(Index: Integer): Condition;
   property Items[Index: Integer]: Condition read GetItem write SetItem; default;
   //
   function GetUsedIndexes: String;
 end;

 TExtCondFunc = function(const ACondition: Condition): Boolean of object;

 anAction = class(TCollectionItem)
  private
   FEnabled: Boolean;
   FActType: Integer; //one of act*
   FDest: String;
   FParam: String;
   FFlag: Boolean;
   FExisting: Integer; //one of actex*
  public
   procedure Assign(Source: TPersistent); override;
   procedure Clear;
  published
   property Enabled: Boolean read FEnabled write FEnabled;
   property ActType: Integer read FActType write FActType;
   property Destination: String read FDest write FDest;
   property Parameter: String read FParam write FParam;
   property Flag: Boolean read FFlag write FFlag;
   property Existing: Integer read FExisting write FExisting;
 end;

 TActions = class(TCollection)
  private
   function GetItem(Index: Integer): anAction;
   procedure SetItem(Index: Integer; const Value: anAction);
  public
   function Add: anAction;
   function Insert(Index: Integer): anAction;
   property Items[Index: Integer]: anAction read GetItem write SetItem; default;
 end;
 {
 ExternalFunction = class(TCollectionItem)
  private
   FFunc: TExtCondFunc;
   FType: Integer;
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   property Func: TExtCondFunc read FFunc write FFunc;
   property FuncType: Integer read FType write FType;
 end;

 TExternalFunctions = class(TCollection)
  private
   function GetItem(Index: Integer): ExternalFunction;
   procedure SetItem(Index: Integer; const Value: ExternalFunction);
  public
   function Add(AFunction: TExtCondFunc; AType: Integer): ExternalFunction;
   function Insert(Index: Integer): ExternalFunction;
   property Items[Index: Integer]: ExternalFunction read GetItem write SetItem; default;
   //
   function IndexOfType(AType: Integer): Integer;
 end;
 }
 Rule = class(TCollectionItem)
  private
   FEnabled: Boolean;
   FTitle: String;
   FFormula: String;
   FOthers: Boolean;
   FConditions: TConditions;
   FActions: TActions;
   FFunction: TExtCondFunc;
  public
   constructor Create(Collection: TCollection); override;
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
   //
   function Matched(const Info: String): Boolean;
   property ExtFunction: TExtCondFunc read FFunction write FFunction;
  published
   property Enabled: Boolean read FEnabled write FEnabled;
   property Title: String read FTitle write FTitle;
   property Formula: String read FFormula write FFormula;
   property Others: Boolean read FOthers write FOthers;
   property Conditions: TConditions read FConditions write FConditions;
   property Actions: TActions read FActions write FActions;
 end;

 TRules = class(TCollection)
  private
   FOnDelete: TNotifyEvent;
   function GetItem(Index: Integer): Rule;
   procedure SetItem(Index: Integer; const Value: Rule);
  protected
   procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
  public
   function Add(AFunction: TExtCondFunc): Rule;
   function Insert(Index: Integer): Rule;
   property Items[Index: Integer]: Rule read GetItem write SetItem; default;
   //
   function IndexOfTitle(const ATitle: String): Integer;
   property OnDelete: TNotifyEvent read FOnDelete write FOnDelete default nil;
 end;

 RulesCollection = class(TPersistent)
  private
   FSilent: Boolean;
   FLog: Integer;
   FLangMode: Integer;
   FLangCode: String;
   FLogDump: Integer;
   FLogDM: Boolean;
   FRules: TRules;
   FRuleByDefault: Boolean;
   FActionByDefault: Boolean;
   FSavedReturn: Integer;
  public
   constructor Create(AOwner: TComponent);
   destructor Destroy; override;
   procedure Assign(Source: TPersistent); override;
  published
   property Silent: Boolean read FSilent write FSilent;
   property Log: Integer read FLog write FLog;
   property LangMode: Integer read FLangMode write FLangMode;
   property LangCode: String read FLangCode write FLangCode;
   property LogDump: Integer read FLogDump write FLogDump;
   property LogDM: Boolean read FLogDM write FLogDM;
   property Rules: TRules read FRules write FRules;
   property RuleByDefault: Boolean read FRuleByDefault write FRuleByDefault default False;
   property ActionByDefault: Boolean read FActionByDefault write FActionByDefault default false;
   property SavedReturn: Integer read FSavedReturn write FSavedReturn default 0;
 end;

 Process = class(TCollectionItem)
  private
   FInfo: String;
   FRuleID: Integer;
   FActionID: Integer;
   FDone: Integer;
   FSEI: TShellExecuteInfo;
   //procedure SetSEI(ASEI: TShellExecuteInfo);
  public
   constructor Create(Collection: TCollection); override;
   procedure Assign(Source: TPersistent); override;
   procedure SEI_Clear;
  published
   property Info: String read FInfo write FInfo;
   property RuleID: Integer read FRuleID write FRuleID;
   property ActionID: Integer read FActionID write FActionID;
   property Done: Integer read FDone write FDone;
   property SEI: TShellExecuteInfo read FSEI write FSEI;
 end;

 TProcesses = class(TCollection)
  private
   function GetItem(Index: Integer): Process;
   procedure SetItem(Index: Integer; const Value: Process);
  public
   function Add: Process;
   function Insert(Index: Integer): Process;
   property Items[Index: Integer]: Process read GetItem write SetItem; default;
 end;

 ERuleException = class (Exception);

const
 PluginTextDomain = 'AfterDownload';

 cndIndex = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 //condition types
 cndCategory = 1;
 cndCategoryDesc = 'Is from category';
 cndURL = 2;
 cndURLDesc = 'Has URL';
 cndFolder = 3;
 cndFolderDesc = 'Saved to folder';
 cndFilename = 4;
 cndFilenameDesc = 'Has filename';
 cndSizeLT = 10;
 cndSizeLTDesc = 'Size is less than';
 //cndSizeLE = 11;
 //cndSizeLEDesc =  'Size is less or equal to';
 cndSizeEQ = 12;
 cndSizeEQDesc = 'Size is equal to';
 //cndSizeGE = 13;
 //cndSizeGEDesc = 'Size is greater or equal to';
 cndSizeGT = 14;
 cndSizeGTDesc = 'Size is greater than';
 cndFlagExists = 100;
 cndFlagExistsDesc = 'Flag exists';
 //cndFlagDontExists = 101;
 //cndFlagDontExistsDesc = 'Flag don''t exists';
 cndCategoryHas = 5;
 cndCategoryHasDesc = 'Category has downloads';
 cndReferer = 6;
 cndRefererDesc = 'Has referer';
 cndDescription = 7;
 cndDescriptionDesc = 'Has description';

 //action types
 actCopy = 1;
 actCopyDesc = 'Copy file to';
 actMove = 2;
 actMoveDesc = 'Move file to';
 actLaunch = 3;
 actLaunchDesc = 'Launch program';
 actStart = 4;
 actStartDesc = 'Start download the category';
 actFlagCreate = 5;
 actFlagCreateDesc = 'Create flag';
 actFlagDelete = 6;
 actFlagDeleteDesc = 'Delete file to Recycle';
 actRename = 7;
 actRenameDesc = 'Rename to';
 actFileDeletePerm = 8;
 actFileDeletePermDesc = 'Delete file permanently';
 actDownloadedDelete = 9;
 actDownloadedDeleteDesc = 'Delete downloaded file';
 actRuleEnable = 10;
 actRuleEnableDesc = 'Enable rule';
 actRuleDisable = 11;
 actRuleDisableDesc = 'Disable rule';
 actRuleDelete = 12;
 actRuleDeleteDesc = 'Delete rule';
 actStartAll = 13;
 actStartAllDesc = 'Start all available downloads';
 actStopAll = 14;
 actStopAllDesc = 'Stop all downloads';
 actStartStopped = 15;
 actStartStoppedDesc = 'Start downloads stopped before';
 actSpeedDescCommon = 'Change speed to %s';
 actSpeedMax = 16;
 actSpeedMaxDesc = 'maximum';
 ActSpeedMed = 17;
 ActSpeedMedDesc = 'medium';
 actSpeedMin = 18;
 actSpeedMinDesc = 'minimum';
 actSpeedAuto = 19;
 actSpeedAutoDesc = 'automatic';
 actSpeedAdj = 20;
 actSpeedAdjDesc = 'adjustable';
 actSpeedLast = 21;
 actSpeedLastDesc = 'last used';
 actStopCategory = 22;
 actStopCategoryDesc = 'Stop download the category';

 actexRename = 0;
 actexAsk = 1;
 actexOverwrite = 2;

 XMLName = 'AfterDownload.xml';
 SilentDefault = true;

 ridDelete = -3;
 ridNotSaved = -2;
 ridNoRule = -1;

 aidComplete = -2;
 aidNoAction = -1;

 dNotStarted = -1;
 dDone = 0;
 dContinue = 1;

 //lang modes
 lmByDM = 0;
 lmAvail = 1;

 //saved
 cSavedFIFO = 0;
 cSavedLIFO = 1;

 //macros
 tknPath = '%P';
 tknFolder = '%F';
 tknName = '%N';
 tknNameOnly = '%O';
 tknExtension = '%E';

var
 PluginsPath: String;
 CatList: TStringList = nil;

 myRules: RulesCollection = nil;
 CS_Rules: TRTLCriticalSection;

 myFunction: TExtCondFunc = nil;
 myCatFunction: TCatInfo = nil;

 myLocales: TLocales = nil;
 myInfo: TStringList = nil;

function TrimQuotes(AStr: String): String;
//
function GetCondTypeDesc(ACondType: Integer): String;
function GetAvailCondIndex(Used: String): Char;
//
function GetActTypeDesc(AActType: Integer): String;
function GetActTypeSpeedDesc(AActType: Integer): String;
function GetActTypeFlagDesc(AActType: Integer; Flag: Boolean): String;
//
function IsProcessActive(ASEI: TShellExecuteInfo): Boolean;
//
function IndexOfValue(const Value: String; List: TStrings): Integer;
//
function CompileRE(const AnExpression: string): String;
//
function IsValidFilename(const Filename: string): Boolean;
//
function ExpandMacros(const Path, Macro: string): string;

implementation

uses
 gnugettext,
 Math,
 RegExpr,
 formula,
 Tokens,
 Wizard,
 AfterDownload_Log;

 { Common stuff }

function TrimQuotes(AStr: String): String;
begin
 Result:=AStr;
 if Length(Result)>0 then
  begin
   if Result[1]='"' then
     Delete(Result, 1, 1);
   if Length(Result)>0 then
     if Result[Length(Result)]='"' then
       Delete(Result, Length(Result), 1);
  end;
end;

function GetCondTypeDesc(ACondType: Integer): String;
begin
 case ACondType of
  cndCategory: Result:=_(cndCategoryDesc);
  cndCategoryHas: Result:=_(cndCategoryHasDesc);
  cndURL: Result:=_(cndURLDesc);
  cndFolder: Result:=_(cndFolderDesc);
  cndFilename: Result:=_(cndFilenameDesc);
  cndSizeLT: Result:=_(cndSizeLTDesc);
  //cndSizeLE: Result:=_(cndSizeLEDesc);
  cndSizeEQ: Result:=_(cndSizeEQDesc);
  //cndSizeGE: Result:=_(cndSizeGEDesc);
  cndSizeGT: Result:=_(cndSizeGTDesc);
  cndFlagExists: Result:=_(cndFlagExistsDesc);
  //cndFlagDontExists: Result:=_(cndFlagDontExistsDesc);
  cndReferer: Result:=_(cndRefererDesc);
  cndDescription: Result:=_(cndDescriptionDesc);
  else
    Result:=_('Unknown type');
  end;
end;

function GetAvailCondIndex(Used: String): Char;
 var
  i: Integer;
begin
 Result:=#0;
 i:=1;
 repeat
  if Pos(cndIndex[i], Used)=0 then
    Result:=cndIndex[i];
  Inc(i);
 until (Result<>#0) or (i>=Length(cndIndex));
end;

function GetActTypeDesc(AActType: Integer): String;
begin
 case AActType of
  actCopy: Result:=_(actCopyDesc);
  actMove: Result:=_(actMoveDesc);
  actLaunch: Result:=_(actLaunchDesc);
  actStart: Result:=_(actStartDesc);
  actFlagCreate: Result:=_(actFlagCreateDesc);
  actFlagDelete: Result:=_(actFlagDeleteDesc);
  actRename: Result:=_(actRenameDesc);
  actFileDeletePerm: Result:=_(actFileDeletePermDesc);
  actDownloadedDelete: Result:=_(actDownloadedDeleteDesc);
  actRuleEnable: Result:=_(actRuleEnableDesc);
  actRuleDisable: Result:=_(actRuleDisableDesc);
  actRuleDelete: Result:=_(actRuleDeleteDesc);
  actStartAll: Result:=_(actStartAllDesc);
  actStopAll: Result:=_(actStopAllDesc);
  actStartStopped: Result:=_(actStartStoppedDesc);
  actSpeedMax: Result:=Format(_(actSpeedDescCommon), [_(actSpeedMaxDesc)]);
  ActSpeedMed: Result:=Format(_(actSpeedDescCommon), [_(ActSpeedMedDesc)]);
  actSpeedMin: Result:=Format(_(actSpeedDescCommon), [_(actSpeedMinDesc)]);
  actSpeedAuto: Result:=Format(_(actSpeedDescCommon), [_(actSpeedAutoDesc)]);
  actSpeedAdj: Result:=Format(_(actSpeedDescCommon), [_(actSpeedAdjDesc)]);
  actSpeedLast: Result:=Format(_(actSpeedDescCommon), [_(actSpeedLastDesc)]);
  actStopCategory: Result:=_(actStopCategoryDesc);
  else
    Result:=_('Unknown type');
  end;
end;

function GetActTypeSpeedDesc(AActType: Integer): String;
begin
 case AActType of
  actSpeedMax: Result:=_(actSpeedMaxDesc);
  ActSpeedMed: Result:=_(ActSpeedMedDesc);
  actSpeedMin: Result:=_(actSpeedMinDesc);
  actSpeedAuto: Result:=_(actSpeedAutoDesc);
  actSpeedAdj: Result:=_(actSpeedAdjDesc);
  else Result:='';
  end;
end;

function GetActTypeFlagDesc(AActType: Integer; Flag: Boolean): String;
begin
 if Flag then
  begin
   case AActType of
    actCopy, actMove: Result:=_('Save target path for following actions');
    actLaunch: Result:=_('wait for close');
    actStart: Result:=_('first available download only');
    actStopAll: Result:=_('Save list of stopped to next actions');
    actSpeedMax, actSpeedMed, actSpeedMin, actSpeedAuto, actSpeedAdj: Result:=_('Save current to next actions');
    actRename: Result:=_('Save target filename for following actions');
    actDownloadedDelete: Result:=_('Permanently');
    else Result:='';
    end;
   if Result<>'' then
     Result:='('+Result+')';
  end
 else
   Result:='';
end;

function IsProcessActive(ASEI: TShellExecuteInfo): Boolean;
 var
  exitcode: Cardinal;
begin
 if ASEI.hProcess=0 then
   Result:=false
 else
   Result:=GetExitCodeProcess(ASEI.hProcess, exitcode) and (exitcode=STILL_ACTIVE);
end;

function IndexOfValue(const Value: String; List: TStrings): Integer;
 var
  i: Integer;
begin
 if Assigned(List) then
  begin
   i:=List.Count-1;
   repeat
    Result:=i;
    Dec(i);
   until (Result<0) or (List.ValueFromIndex[Result]=Value);
  end
 else
   Result:=-2;
end;

function CompileRE(const AnExpression: string): String;
 var
   re: TRegExpr;
begin
 Result:='';
 re:=TRegExpr.Create;
 re.ModifierI:=true; //case insensitive search
 re.Expression:=AnExpression;
 try
  re.Compile;
 except
  on E: Exception do
    Result:=Copy(E.Message, Pos(':', E.Message)+2, Length(E.Message));
 end;
 FreeAndNil(re);
end;

function IsValidFilename(const Filename: string): Boolean;
 const
   Invalid: TCharSet = ['\', '/', ':', '*', {'.', }'?', '"', '<', '>', '|'];
 var
   i: Integer;
begin
 if Filename='' then
   Result:=False
 else
  begin
   Result:=true;
   for i:=1 to Length(Filename) do
    begin
     if Filename[i] in Invalid then
      begin
       Result:=false;
       Break;
      end;
    end;
  end;
end;

function ExpandMacros(const Path, Macro: string): string;
 var
   FF, FN, FNO, FE: String;
begin
 FF:=IncludeTrailingPathDelimiter(ExtractFilePath(Path));
 FN:=ExtractFileName(Path);
 FE:=ExtractFileExt(FN);
 if FE='' then
   FNO:=FN
 else
   FNO:=Copy(FN, 1, Pos(FE, FN)-1);
 //
 Result:=Macro;
 ReplaceEx(Result, tknPath, Path);
 ReplaceEx(Result, tknFolder, FF);
 ReplaceEx(Result, tknName, FN);
 ReplaceEx(Result, tknNameOnly, FNO);
 ReplaceEx(Result, tknExtension, FE);
end;

 { Condition }

constructor Condition.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 CondIndex:=#0;
 CondType:=-1;
 Parameter:='';
 ExternalFlag:=false;
end;

procedure Condition.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.CondIndex:=(Source as Condition).CondIndex;
     Self.CondType:=(Source as Condition).CondType;
     Self.Parameter:=(Source as Condition).Parameter;
     Self.ExternalFlag:=(Source as Condition).ExternalFlag;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure Condition.Clear;
begin
 CondIndex:=#0;
 CondType:=0;
 Parameter:='';
end;

 { TConditions }

function TConditions.Add: Condition;
begin
 Result := Condition(inherited Add);
end;

function TConditions.GetItem(Index: Integer): Condition;
begin
 Result := Condition(inherited Items[Index]);
end;

function TConditions.Insert(Index: Integer): Condition;
begin
 Result := Condition(inherited Insert(Index));
end;

procedure TConditions.SetItem(Index: Integer; const Value: Condition);
begin
 Items[Index].Assign(Value);
end;

function TConditions.GetUsedIndexes: String;
 var
  i: Integer;
begin
 Result:='';
 for i:=0 to Count-1 do
   Result:=Result+Items[i].CondIndex;
end;

 { anAction }

procedure anAction.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Enabled:=(Source as anAction).Enabled;
     Self.ActType:=(Source as anAction).ActType;
     Self.Destination:=(Source as anAction).Destination;
     Self.Parameter:=(Source as anAction).Parameter;
     Self.Flag:=(Source as anAction).Flag;
     Self.Existing:=(Source as anAction).Existing;
    end
   else
     inherited Assign(Source);
  end;
end;

procedure anAction.Clear;
begin
 Enabled:=false;
 ActType:=0;
 Destination:='';
 Parameter:='';
 Flag:=false;
 Existing:=0;
end;

 { TActions }

function TActions.Add: anAction;
begin
 Result := anAction(inherited Add);
end;

function TActions.GetItem(Index: Integer): anAction;
begin
 Result := anAction(inherited Items[Index]);
end;

function TActions.Insert(Index: Integer): anAction;
begin
 Result := anAction(inherited Insert(Index));
end;

procedure TActions.SetItem(Index: Integer; const Value: anAction);
begin
 Items[Index].Assign(Value);
end;

(*
 { ExternalFunction }

constructor ExternalFunction.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 Func:=nil;
 FuncType:=-1;
end;

procedure ExternalFunction.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Func:=(Source as ExternalFunction).Func;
     Self.FuncType:=(Source as ExternalFunction).FuncType;
    end
   else
     inherited Assign(Source);
  end;
end;

{ TExternalFunctions }

function TExternalFunctions.Add(AFunction: TExtCondFunc; AType: Integer): ExternalFunction;
begin
 Result := ExternalFunction(inherited Add);
 Result.Func:=AFunction;
 Result.FuncType:=AType;
end;

function TExternalFunctions.GetItem(Index: Integer): ExternalFunction;
begin
 Result := ExternalFunction(inherited Items[Index]);
end;

function TExternalFunctions.Insert(Index: Integer): ExternalFunction;
begin
 Result := ExternalFunction(inherited Insert(Index));
end;

procedure TExternalFunctions.SetItem(Index: Integer; const Value: ExternalFunction);
begin
 Items[Index].Assign(Value);
end;

function TExternalFunctions.IndexOfType(AType: Integer): Integer;
 var
  i: Integer;
begin
 Result:=-1;
 i:=Count-1;
 repeat
  if i>=0 then
   begin
    if Items[i].FuncType=AType then
      Result:=i;
   end;
  Dec(i);
 until (i<0) or (Result>=0);
end;
*)
{ Rule }

constructor Rule.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 Title:='';
 Formula:='';
 Others:=false;
 Conditions := TConditions.Create(Condition);
 Actions := TActions.Create(anAction);
 ExtFunction := nil;
end;

destructor Rule.Destroy;
begin
 Conditions.Free;
 Actions.Free;
 ExtFunction:=nil;
 inherited Destroy;
end;

procedure Rule.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Enabled:=(Source as Rule).Enabled;
     Self.Title:=(Source as Rule).Title;
     Self.Formula:=(Source as Rule).Formula;
     Self.Others:=(Source as Rule).Others;
     Self.Conditions.Assign((Source as Rule).Conditions);
     Self.Actions.Assign((Source as Rule).Actions);
     Self.ExtFunction:=(Source as Rule).ExtFunction;
    end
   else
     inherited Assign(Source);
  end;
end;

function Rule.Matched(const Info: String): Boolean;
 var
  AF: TArtFormula;
  re: TRegExpr;
  vars: array of String;
  vals: TCalcArray;
  i, num, valInfo, valCond: Integer;
  s, sCat: String;
  e: ERuleException;
begin
 Result:=false;
 //
 re:=TRegExpr.Create;
 re.ModifierI:=true;
 //
 AF:=TArtFormula.Create(nil);
 AF.TestUsedVars:=true;
 num:=Conditions.Count;
 SetLength(vars, num);
 SetLength(vals, num);
 //fill vals with conditions' values
 for i:=0 to num-1 do
  begin
   vars[i]:=Conditions[i].CondIndex;
   //external function call
   if Conditions[i].ExternalFlag then
    begin
     {
     if Assigned(ExtFunction) then
       setN(vals[i], IfThen(ExtFunction(Conditions[i]), 1, 0))
     else
       setN(vals[i], 0); //no function defined
     //
     Continue;
     }
     if Assigned(ExtFunction) then
       setN(vals[i], IfThen(ExtFunction(Conditions[i]), 1, 0))
     else
      begin
       e:=ERuleException.CreateFmt(_('External function is not assigned in rule %s.'), [Self.Title]);
       raise e;
      end;
     Continue;
    end;
   //general function call
   case Conditions[i].CondType of
    cndCategory:
     begin
      s:=ExtractToken('categoryid', Info);
      sCat:=CatList.Values[s];
      if sCat='' then
        setN(vals[i], 0) //no category with specified index
      else
        setN(vals[i], IfThen(sCat=Conditions[i].Parameter, 1, 0));
     end;
    cndURL:
     begin
      s:=ExtractToken('url', Info);
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         //disable rule on regexp error
         Self.Enabled:=false;
         setN(vals[i], 0);
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
           Log(cLogRule, Format(_('Rule "%s" is disabled: regexp error "%s" with regexp "%s".'), [Title, E.Message, s]));
        end;
      end;
     end;
    cndFolder:
     begin
      s:=ExtractFilePath(ExtractToken('saveto', Info));
      setN(vals[i], IfThen(SameText(s, Conditions[i].Parameter), 1, 0));
     end;
    cndFilename:
     begin
      s:=ExtractFileName(ExtractToken('saveto', Info));
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         //disable rule on regexp error
         Self.Enabled:=false;
         setN(vals[i], 0);
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
           Log(cLogRule, Format(_('Rule "%s" is disabled: regexp error "%s" with regexp "%s".'), [Title, E.Message, s]));
        end;
      end;
     end;
    cndReferer:
     begin
      s:=ExtractFileName(ExtractToken('referer', Info));
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         //disable rule on regexp error
         Self.Enabled:=false;
         setN(vals[i], 0);
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
           Log(cLogRule, Format(_('Rule "%s" is disabled: regexp error "%s" with regexp "%s".'), [Title, E.Message, s]));
        end;
      end;
     end;
    cndDescription:
     begin
      s:=ExtractFileName(ExtractToken('description', Info));
      re.Expression:=Conditions[i].Parameter;
      try
       setN(vals[i], IfThen(re.Exec(s), 1, 0));
      except
       on E:Exception do
        begin
         //disable rule on regexp error
         Self.Enabled:=false;
         setN(vals[i], 0);
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
           Log(cLogRule, Format(_('Rule "%s" is disabled: regexp error "%s" with regexp "%s".'), [Title, E.Message, s]));
        end;
      end;
     end;
    cndSizeLT,{ cndSizeLE,} cndSizeEQ,{ cndSizeGE,} cndSizeGT:
     begin
      s:=ExtractToken('size', Info);
      valInfo:=StrToIntDef(s, -1);
      if valInfo<0 then
        setN(vals[i], 0) //error in info
      else
       begin
        valCond:=StrToIntDef(Conditions[i].Parameter, -1);
        if valCond<0 then
         begin
          //error in parameter - disable rule
          Self.Enabled:=false;
          setN(vals[i], 0);
          //
          if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
            Log(cLogRule, Format(_('Rule "%s" is disabled: Invalid size parameter "%s" in condition "%s".'), [Title, Conditions[i].Parameter, GetCondTypeDesc(Conditions[i].CondType)]));
         end
        else
         begin
          case Conditions[i].CondType of
           cndSizeLT: setN(vals[i], IfThen(valInfo<valCond, 1, 0));
           //cndSizeLE: setN(vals[i], IfThen(valInfo<=valCond, 1, 0));
           cndSizeEQ: setN(vals[i], IfThen(valInfo=valCond, 1, 0));
           //cndSizeGE: setN(vals[i], IfThen(valInfo>=valCond, 1, 0));
           cndSizeGT: setN(vals[i], IfThen(valInfo>valCond, 1, 0));
           end;
         end;
       end;
     end;
    cndFlagExists:
     begin
      setN(vals[i], IfThen(FileExists(Conditions[i].Parameter), 1, 0));
     end;
    {
    cndFlagDontExists:
     begin
      setN(vals[i], IfThen(FileExists(Conditions[i].Parameter), 0, 1));
     end;
    }
    else
      setN(vals[i], 0);
    end;
  end;
 try
  Result:=StrToBoolDef(AF.ComputeStr(Self.Formula, num, @vars, @vals), false);
 except
  on E:Exception do
   begin
    //disable rule on formula error
    Self.Enabled:=false;
   end;
 end;
 FreeAndNil(AF);
 FreeAndNil(re);
end;

{ TRules }

function TRules.Add(AFunction: TExtCondFunc): Rule;
begin
 Result := Rule(inherited Add);
 Result.ExtFunction:=AFunction;
end;

function TRules.GetItem(Index: Integer): Rule;
begin
 Result := Rule(inherited Items[Index]);
end;

function TRules.Insert(Index: Integer): Rule;
begin
 Result := Rule(inherited Insert(Index));
end;

procedure TRules.SetItem(Index: Integer; const Value: Rule);
begin
 Items[Index].Assign(Value);
end;

procedure TRules.Notify(Item: TCollectionItem; Action: TCollectionNotification);
begin
 inherited Notify(Item, Action);
 case Action of
  cnDeleting:
   begin
    if Assigned(FOnDelete) then
      FOnDelete(Self);
   end;
  end;
end;

function TRules.IndexOfTitle(const ATitle: String): Integer;
begin
 Result:=Count-1;
 while Result>=0 do
  begin
   if SameText(Items[Result].Title, ATitle) then
     Break
   else
     Dec(Result);
  end;
end;

{ RulesCollection }

constructor RulesCollection.Create(AOwner: TComponent);
begin
 Silent:=SilentDefault;
 Log:=0;
 LangMode:=0;
 LangCode:='en';
 Rules:=TRules.Create(Rule);
 SavedReturn:=cSavedFIFO;
end;

destructor RulesCollection.Destroy;
begin
 Rules.Free;
 inherited Destroy;
end;

procedure RulesCollection.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Silent:=(Source as RulesCollection).Silent;
     Self.Log:=(Source as RulesCollection).Log;
     Self.LangMode:=(Source as RulesCollection).LangMode;
     Self.LangCode:=(Source as RulesCollection).LangCode;
     Self.LogDump:=(Source as RulesCollection).LogDump;
     Self.LogDM:=(Source as RulesCollection).LogDM;
     Self.Rules.Assign((Source as RulesCollection).Rules);
     Self.RuleByDefault:=(Source as RulesCollection).RuleByDefault;
     Self.ActionByDefault:=(Source as RulesCollection).ActionByDefault;
     Self.SavedReturn:=(Source as RulesCollection).SavedReturn;
    end;
  end;
end;

 { Process }

constructor Process.Create(Collection: TCollection);
begin
 inherited Create(Collection);
 FInfo:='';
 FRuleID:=-1;
 FActionID:=-1;
 FDone:=dNotStarted;
 SEI_Clear;
end;

procedure Process.Assign(Source: TPersistent);
begin
 if Assigned(Source) then
  begin
   if Source.ClassNameIs(Self.ClassName) then
    begin
     Self.Info:=(Source as Process).Info;
     Self.RuleID:=(Source as Process).RuleID;
     Self.ActionID:=(Source as Process).ActionID;
     Self.Done:=(Source as Process).Done;
     Self.SEI:=(Source as Process).SEI;
     {
     Self.FSEI.cbSize:=(Source as Process).FSEI.cbSize;
     Self.FSEI.fMask:=(Source as Process).FSEI.fMask;
     Self.FSEI.Wnd:=(Source as Process).FSEI.Wnd;
     Self.FSEI.lpVerb:=(Source as Process).FSEI.lpVerb;
     Self.FSEI.lpFile:=(Source as Process).FSEI.cbSize;
     Self.FSEI.lpParameters:=(Source as Process).FSEI.cbSize;
     Self.FSEI.lpDirectory:=(Source as Process).FSEI.cbSize;
     Self.FSEI.nShow:=(Source as Process).FSEI.cbSize;
     Self.FSEI.hInstApp:=(Source as Process).FSEI.cbSize;
     Self.FSEI.lpIDList:=(Source as Process).FSEI.cbSize;
     Self.FSEI.lpClass:=(Source as Process).FSEI.cbSize;
     Self.FSEI.hkeyClass:=(Source as Process).FSEI.cbSize;
     Self.FSEI.dwHotKey:=(Source as Process).FSEI.cbSize;
     Self.FSEI.hIcon:=(Source as Process).FSEI.cbSize;
     Self.FSEI.hProcess:=(Source as Process).FSEI.cbSize;
     }
    end
   else
     inherited Assign(Source);
  end;
end;

procedure Process.SEI_Clear;
begin
 FillChar(FSEI, SizeOf(TShellExecuteInfo), 0);
end;

{ TProcesses }

function TProcesses.Add: Process;
begin
 Result := Process(inherited Add);
end;

function TProcesses.GetItem(Index: Integer): Process;
begin
 Result := Process(inherited Items[Index]);
end;

function TProcesses.Insert(Index: Integer): Process;
begin
 Result := Process(inherited Insert(Index));
end;

procedure TProcesses.SetItem(Index: Integer; const Value: Process);
begin
 Items[Index].Assign(Value);
end;

end.
